package yh.aop.demo.aop;

import com.github.benmanes.caffeine.cache.Cache;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import yh.aop.demo.Annotation.DoubleCache;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

/**
 * @author yangheng
 * @version 1.0
 * @date 2024/7/15 20:59
 */

/**
 * 1.1 导入依赖 设置好对应的注解修饰
 */
@Aspect
@Component
@Slf4j
public class DoubleCacheAspect {


    @Resource
    private Cache<String, Object> caffeineCache;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 1.2 定义切点
     */
    @Pointcut("@annotation(yh.aop.demo.Annotation.DoubleCache)")
    public void doubleCachePointCut() {
    }

    @Around("doubleCachePointCut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        // 获取参数的名称 这里就是request 这个名字
        String[] paramNames = signature.getParameterNames();
        // 获取参数名称在内存中对应的值
        Object[] args = point.getArgs();
        TreeMap<String, Object> treeMap = new TreeMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            treeMap.put(paramNames[i], args[i]);
        }
        DoubleCache annotation = method.getAnnotation(DoubleCache.class);
        String elResult = arrayParse(Arrays.asList(annotation.key()), treeMap);
        String realKey = annotation.cacheName() + "redis:clone" + elResult;
        // 强制更新
        if (annotation.type() == DoubleCache.CacheType.PUT) {
            Object object = point.proceed();
            redisTemplate.opsForValue().set(realKey, object, annotation.expireTime(), TimeUnit.SECONDS);
            caffeineCache.put(realKey, object);
            return object;
        }
        // 删除
        else if (annotation.type() == DoubleCache.CacheType.DELETE) {
            redisTemplate.delete(realKey);
            caffeineCache.invalidate(realKey);
            return point.proceed();
        }
        // 读写，查询Caffeine
        Object caffeineCacheObj = caffeineCache.getIfPresent(realKey);
        if (Objects.nonNull(caffeineCacheObj)) {
            log.info("get data from caffeine");
            return caffeineCacheObj;
        }
        // 查询Redis
        Object redisCache = redisTemplate.opsForValue().get(realKey);
        if (Objects.nonNull(redisCache)) {
            log.info("get data from redis");
            caffeineCache.put(realKey, redisCache);
            return redisCache;
        }
        log.info("get data from database");
        Object object = point.proceed();
        if (Objects.nonNull(object)) {
            // 写入Redis
            log.info("get data from database write to cache: {}", object);
            redisTemplate.opsForValue().set(realKey, object, annotation.expireTime(), TimeUnit.SECONDS);
            // 写入Caffeine
            caffeineCache.put(realKey, object);
        }
        return object;
    }

    private String arrayParse(List<String> elStrings, SortedMap<String, Object> map) {
        List<String> result = Collections.emptyList();
        elStrings.forEach(elString -> {
            // elString会填充 %s  #{#request.city}
            elString = String.format("#{%s}", elString);
            // 创建表达式解析器
            ExpressionParser parser = new SpelExpressionParser();
            // 通过evaluationContext.setVariable可以在上下文中设定变量。
            EvaluationContext context = new StandardEvaluationContext();
            map.forEach(context::setVariable);
            // 解析表达式
            Expression expression = parser.parseExpression(elString, new TemplateParserContext());
            // 使用Expression.getValue()获取表达式的值，这里传入了Evaluation上下文
            result.add(expression.getValue(context, String.class));
        });
        return String.join("redis:clone", result);
    }


}
